package com.vexdev.logging;

import android.os.Bundle;
import android.util.Log;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


/**
 * Created by Luca Vitucci on 21/06/13.
 *
 * New Logging guideline:
 * - Always use this class for logging
 */
public class L {

    private static RemoteLogger sRemoteLogger;
    private static String sUserID;
    private static String sUsername;
    private static String sUserEmail;

    private static final int CALLER_STACKTRACE_LEVEL = 4;

    /************************************************************************************************\
     |                                      LOGGING METHODS                                           |
     \************************************************************************************************/

    /**
     * Logs an error to Logcat and Remote logs.
     * @param errorMessage Error message to log
     */
    public static void e(@NotNull String errorMessage) {
        Log.e(getCallingClassName(), errorMessage);
        if(remote()) {
            sRemoteLogger.onError(getCallingClassName(),errorMessage);
        }
    }

    /**
     * Logs an error to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param errorMessage Error message to log
     */
    public static void e(@NotNull String tag, @NotNull String errorMessage) {
        Log.e(tag, errorMessage);
        if(remote()) {
            sRemoteLogger.onError(tag,errorMessage);
        }
    }

    /**
     * Logs an error to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param errorMessage Error message to log
     * @param throwable Throwable to log
     */
    public static void e(@NotNull String tag, @NotNull String errorMessage, @Nullable Throwable throwable) {
        Log.e(tag, errorMessage, throwable);
        if(remote()) {
            sRemoteLogger.onError(tag, errorMessage, throwable);
        }
    }

    /**
     * Logs a warning message to Logcat and Remote logs.
     * @param message Message to log
     */
    public static void w(@NotNull String message) {
        Log.w(getCallingClassName(), message);
        if(remote()) {
            sRemoteLogger.onWarning(getCallingClassName(), message);
        }
    }

    /**
     * Logs a warning message to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param message Message to log
     */
    public static void w(@NotNull String tag, @NotNull String message) {
        Log.w(tag, message);
        if(remote()) {
            sRemoteLogger.onWarning(tag, message);
        }
    }

    /**
     * Logs an info message to Logcat and Remote logs.
     * @param message Message to log
     */
    public static void i(@NotNull String message) {
        Log.i(getCallingClassName(), message);
        if(remote()) {
            sRemoteLogger.onInfo(getCallingClassName(), message);
        }
    }

    /**
     * Logs an info message to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param message Message to log
     */
    public static void i(@NotNull String tag, @NotNull String message) {
        Log.i(tag, message);
        if(remote()) {
            sRemoteLogger.onInfo(tag, message);
        }
    }

    /**
     * Logs a debug message to Logcat and Remote logs.
     * @param message Message to log
     */
    public static void d(@NotNull String message) {
        Log.d(getCallingClassName(), message);
        if(remote()) {
            sRemoteLogger.onDebug(getCallingClassName(), message);
        }
    }

    /**
     * Logs a debug message to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param message Message to log
     */
    public static void d(@NotNull String tag, @NotNull String message) {
        Log.d(tag, message);
        if(remote()) {
            sRemoteLogger.onDebug(tag, message);
        }
    }

    /**
     * Logs a verbose message to Logcat and Remote logs.
     * @param message Message to log
     */
    public static void v(@NotNull String message) {
        Log.v(getCallingClassName(), message);
        if(remote()) {
            sRemoteLogger.onVerbose(getCallingClassName(), message);
        }
    }

    /**
     * Logs a verbose message to Logcat and Remote logs.
     * @param tag Tag to log.
     * @param message Message to log
     */
    public static void v(@NotNull String tag, @NotNull String message) {
        Log.v(tag, message);
        if(remote()) {
            sRemoteLogger.onVerbose(tag, message);
        }
    }

    /**
     * Logs a method to Logcat and Remote logs.
     */
    public static void m() {
        d(getCallingClassName(), "--METHOD-->"+getCallingMethodName());
    }

    /**
     * Logs a method to Logcat and Remote logs.
     * @param objects Parameters or parameters names
     */
    public static void m(Object ... objects) {
        String parameters = "";
        for(Object o : objects) {
            parameters = parameters.concat(" [" + o.toString() + "]");
        }
        d(getCallingClassName(), "--METHOD-->"+getCallingMethodName()+"--PARAMETERS-->("+parameters+")");
    }

    /**
     * Logs a state change (Anchor)
     */
    public static void a(@NotNull String anchorName) {
        v(getCallingClassName(), "Reached anchor: "+anchorName);
        if(remote()) {
            sRemoteLogger.onAnchor(anchorName);
        }
    }

    /**
     * Logs a key/value pair
     */
    public static void k(@NotNull String key, @NotNull String value) {
        v(getCallingClassName(), key+" : "+value);
        if(remote()) {
            sRemoteLogger.onKey(key, value);
        }
    }

    /************************************************************************************************\
    |                                SPECIAL LOGGING METHODS                                         |
    \************************************************************************************************/

    public static void logBundle(@NotNull Bundle bundle) {
        for(String key : bundle.keySet()) {
            String string = null;
            Bundle inBundle = null;
            Object object = bundle.get(key);
            if(object instanceof Bundle) inBundle = bundle.getBundle(key);
            else if(object instanceof String) string = bundle.getString(key);

            if(string!=null) {
                d("null", "logBundle: "+key+" : "+string);
            } else if (inBundle!=null) {
                d("null", ">>GOING INSIDE>>: "+key);
                logBundle(inBundle);
            }
        }
    }

    public static void logInputStream(@Nullable InputStream inputStream) {
        if(inputStream == null) {
            d(getCallingClassName(), "InputStream: null");
        } else {
            BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder total = new StringBuilder();
            String line;
            try {
                while ((line = r.readLine()) != null) {
                    total.append(line);
                }
            } catch (IOException e) {
                e(getCallingClassName(), e.getLocalizedMessage(), e);
            }
            d(getCallingClassName(), "InputStream: " + total);
        }
    }

    /************************************************************************************************\
    |                              USER IDENTIFICATION METHODS                                       |
    \************************************************************************************************/

    /**
     * Stores the user ID for future uses and sends it to remote loggers.
     * @param userID User Unique Identifier
     */
    public static void userID(@NotNull String userID) {
        sUserID = userID;
        if(remote()) {
            sRemoteLogger.onUserID(sUserID);
        }
    }

    /**
     * Stores the username for future uses and sends it to remote loggers.
     * @param username User Name
     */
    public static void username(@NotNull String username) {
        sUsername = username;
        if(remote()) {
            sRemoteLogger.onUsername(sUsername);
        }
    }

    /**
     * Stores the user's email for future uses and sends it to remote loggers.
     * @param userEmail User Email
     */
    public static void userEmail(@NotNull String userEmail) {
        sUserEmail = userEmail;
        if(remote()) {
            sRemoteLogger.onUserEmail(sUserEmail);
        }
    }

    /************************************************************************************************\
    |                                           METHODS                                              |
    \************************************************************************************************/

    /**
     * Attaches a new remote logger
     * @param logger
     */
    public static void attachRemoteLogger(RemoteLogger logger) {
        if(logger != null) {
            sRemoteLogger = logger;
        }
    }

    /**
     * Detaches a remote logger
     */
    public static void detachRemoteLogger() {
        sRemoteLogger = null;
    }

    /**
     * True if remote logger is attached
     * @return
     */
    private static boolean remote() {
        return sRemoteLogger!=null;
    }

    /**
     * Returns the class name that called a method in com.vexdev.expirometer.utils.L
     * @return
     */
    private static String getCallingClassName() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        return stackTraceElements[CALLER_STACKTRACE_LEVEL].getClassName();
    }

    /**
     * Returns the method name that called a method in com.vexdev.expirometer.utils.L
     * @return
     */
    private static String getCallingMethodName() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        return stackTraceElements[CALLER_STACKTRACE_LEVEL].getMethodName();
    }

    /**
     * This interface is used to attach a Remote logger
     * such as Crashlytics, DeployGate, etc...
     */
    public interface RemoteLogger {
        public void onError(String tag, String errorMessage);
        public void onError(String tag, String errorMessage, Throwable throwable);
        public void onWarning(String tag, String message);
        public void onInfo(String tag, String message);
        public void onDebug(String tag, String message);
        public void onVerbose(String tag, String message);
        public void onAnchor(String anchorName);
        public void onKey(String key, String value);
        public void onUserID(String uid);
        public void onUsername(String username);
        public void onUserEmail(String email);
    }
}
